package tcp;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

/**
 * 基于TCP协议的回显服务器
 * 功能：接收客户端消息并原样返回（回显）
 */
public class TCPEchoServer {
    // 服务器端Socket对象，用于监听指定端口上的客户端连接请求
    protected ServerSocket socket;

    /**
     * 构造方法
     * @param port 服务端的端口号，范围应为1025-65535
     * @throws IOException 如果端口绑定失败或端口号无效
     */
    public TCPEchoServer (int port) throws IOException {
        // 校验端口号
        if (port < 1025 || port > 65535) {
            throw new BindException("端口必须在1025 ~ 65535之间.");
        }
        // 创建ServerSocket对象并绑定到指定端口
        this.socket = new ServerSocket(port);
    }

    /**
     * 启动服务器，开始监听并处理客户端连接请求
     * 此方法会无限循环，持续接收客户端连接
     */
    public void start () throws IOException {
        System.out.println("服务已启动，等待客户端连接.");
        // 无限循环，持续接收客户端连接
        while (true) {
            // 接收客户端请求, 如果没有客户端连接就阻塞等待
            // 当有客户端连接时，返回一个Socket对象用于与客户端通信
            Socket clientSocket = socket.accept();
            // 接收到客户端连接之后，将客户端Socket传递给处理方法进行处理
            processConnections (clientSocket);
        }
    }

    /**
     * 处理客户端连接的专用方法
     * 为了方便维护，专门把数据处理的逻辑抽取成一个方法
     * 负责与客户端进行数据交换（读取请求并发送响应）
     * @param clientSocket 与客户端通信的Socket对象
     */
    protected void processConnections(Socket clientSocket) {
        // 打印客户端连接信息：IP地址和端口号
        System.out.printf("[%s:%d] 客户端上线了.\n",
                clientSocket.getInetAddress().toString(),// 客户端IP地址
                clientSocket.getPort());// 客户端端口号
        // 所有的通信数据都封装在Socket对象中，对应的就是入参clientSocket，TCP传输形式时字节流
        // 1. 获取输入流读取客户端数据，获取输出流向客户端发送数据
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
            // 2. 循环读取客户端发来的数据
            while (true) {
                // 通过Scanner简化文本请求的读取过程
                Scanner scanner = new Scanner(inputStream);
                // 3. 检查输入流中是否还有可读取的数据
                if (!scanner.hasNextLine()) {
                    // 如果没有更多数据，表示客户端已关闭连接
                    System.out.printf("[%s:%d] 客户端下线了.\n",
                            clientSocket.getInetAddress().toString(),
                            clientSocket.getPort());
                    break;// 退出循环，结束与此客户端的通信
                }
                // 4. 获取用户发来的具体内容
                String request = scanner.nextLine();
                // 5. 根据请求来计算响应
                String response = process (request);
                // 6. 把响应写入输出流
                // 6.1 通过PrintWriter来简化写入操作
                PrintWriter writer = new PrintWriter(outputStream);
                // 6.2 向输出流中写入响应
                writer.println(response);
                // 7. 强制刷新缓冲区，相当于系统把数据交给了网卡，也相当于程序把数据发送出去了
                // 确保数据立即发送而不是等待缓冲区满
                writer.flush();
                // 8. 打印日志
                System.out.printf("[%s:%d] request = %s, response = %s\n",
                        clientSocket.getInetAddress().toString(),
                        clientSocket.getPort(), request, response);

            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 处理响应的方式，默认实现为回显请求内容
     * 子类可以重写此方法实现不同的处理逻辑
     * @param request 客户端发送的请求字符串
     * @return 处理后的响应字符串
     */
    protected String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        // 创建服务器实例，监听9999端口
        TCPEchoServer server = new TCPEchoServer(9999);
        // 启动服务器
        server.start();
    }
}
